/**
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.cas.client.jetty;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerWrapper;

import org.jasig.cas.client.session.SessionMappingStorage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.qloudgen.sec.cas.client.session.SessionCallbackHandler;
import org.qloudgen.sec.cas.client.session.CasSingleSignOutHandler;
import org.qloudgen.sec.util.ExceptionUtil;

/**
 * <p>Title: Jetty Client Single Sign Out Handler Implementation</p>
 * <p>Description: Wrappered jetty client single sign out handler.</p>
 * <p>Copyright: qloudgen/sec 2014</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public final class JettySingleSignOutHandler extends HandlerWrapper {
	/**
	 * Log output.
	 */
	private final static Logger logger = LoggerFactory.getLogger( JettySingleSignOutHandler.class );
	/**
	 *
	 */
	private CasSingleSignOutHandler casSingleSignOutHandler = null;
	
	/**
	 * Default constructor.
	 */
	public JettySingleSignOutHandler() {
	}
	
	/**
	 * Sets current cas single sign out handler.
	 *
	 * @param casSingleSignOutHandler
	 */
	public void setCasSingleSignOutHandler(final CasSingleSignOutHandler casSingleSignOutHandler) {
		this.casSingleSignOutHandler = casSingleSignOutHandler;
	}
	
	/**
	 * Returns current cas single sign out handler.
	 *
	 * @return CasSingleSignOutHandler
	 */
	public CasSingleSignOutHandler getCasSingleSignOutHandler() {
		return casSingleSignOutHandler;
	}
	
	/**
	 * Handler start.
	 *
	 * @throws Exception
	 */
	@Override
	protected void doStart() throws Exception {
		super.doStart();
		//
		casSingleSignOutHandler.init();
		logger.info( "Started current cas single sign out handler...{}" , this );
	}
	
	/**
	 * Handler stop.
	 *
	 * @throws Exception
	 */
	@Override
	protected void doStop() throws Exception {
		super.doStop();
		logger.info( "Stopped current cas single sign out handler...{}" , this );
	}

	/**
	 * Handler set server.
	 */
	@Override
	public void setServer(Server server) {
		super.setServer( server );
		logger.info( "Set current cas single sign out handler server...{}" , server );
	}
	
	/**
	 * Handler destroy.
	 */
	@Override
	public void destroy() {
		super.destroy();
		logger.info( "Destroyed current cas single sign out handler...{}" , this );
	}

	/**
	 * Handler handle.
	 *
	 * @param target
	 * @param baseRequest
	 * @param request
	 * @param response
	 * throws IOException
	 * throws ServletException
	 */
	@Override
	public void handle(String target , Request baseRequest , HttpServletRequest request , HttpServletResponse response) throws IOException, ServletException {
		logger.trace( "Current handle target is...{}" , target );
		logger.trace( "Current handle baseRequest is...{}" , baseRequest );
		logger.trace( "Current handle request is...{}" , request );
		logger.trace( "Current handle response is...{}" , response );
		
		/**
		 * Do inject listener.
		 */
		addListener( baseRequest );
		/**
		 * Do record.
		 */
		if ( casSingleSignOutHandler.isTokenRequest( request ) ) {
			logger.debug( "Starting record user session..." );
			casSingleSignOutHandler.recordSession( request );
		}
		/**
		 * Do destroy and break handler chain to application.
		 */
		else if ( casSingleSignOutHandler.isLogoutRequest( request ) ) {
			logger.debug( "Starting destroy user session..." );
			casSingleSignOutHandler.destroySession( request , new JettySessionCallbackHandler( baseRequest ) );
			return;
		}
		/**
		 * Do next handler finally.
		 */
		super.handle(target, baseRequest, request, response);
	}
	
	/**
	 *
	 */
	private boolean isAdded = false;
	private void addListener(Request request) {
		if ( isAdded ) {
			return;
		}
		synchronized ( this ) {
			if ( ! isAdded ) {
				HttpSessionListener listener = new JettySingleSignOutListener();
				request.getSessionManager().addEventListener( listener );
				isAdded = true;
				//
				logger.trace( "Started add inernal session listener...{}" , listener );
			}
		}
	}
	
	/**
	 * Returns the String representation.
	 *
	 * @return String
	 */
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append( "[" );
		builder.append( super.toString() );
		builder.append( "-->" );
		builder.append( "casSingleSignOutHandler=" ).append( casSingleSignOutHandler );
		builder.append( "]" );
		//
		return builder.toString();
	}
	
	/**
	 *
	 */
	private class JettySessionCallbackHandler implements SessionCallbackHandler {
		/**
		 *
		 */
		private Request request = null;
		
		/**
		 * Custom constructor.
		 *
		 * @param request
		 */
		public JettySessionCallbackHandler(Request request) {
			this.request = request;
		}
		
		/**
		 * Retrieve session by id.
		 *
		 * After sign out must close browser that jetty session manager will find session success.
		 *
		 * Use same browser sign in & out again that jetty session manager will destroy session, Why ???
		 * And if after ten minutes do sign in & out again that will be ok !!!
		 * And if use chrome browser that will be always ok !!!
		 *
		 * @param sessionId
		 * @return HttpSession
		 */
		public HttpSession getSessionById(String sessionId) {
			return request.getSessionManager().getHttpSession( sessionId );
		}
	}

	/**
	 *
	 */
	public class JettySingleSignOutListener implements HttpSessionListener {
		/**
		 * Session create event listen.
		 *
		 * @param event
		 */
		public void sessionCreated(final HttpSessionEvent event) {
			logger.info( "Current session is created...{}" , event.getSession().getId() );
		}
		
		/**
		 * Session destroy event listen.
		 *
		 * @param event
		 */
		public void sessionDestroyed(final HttpSessionEvent event) {
			logger.info( "Current session is destroyed...{}" , event.getSession().getId() );
			try {
				/**
				 * Do get current session mapping storage.
				 */
				SessionMappingStorage storage = casSingleSignOutHandler.getSessionMappingStorage();
				/**
				 * Do really session remove.
				 */
				storage.removeBySessionById( event.getSession().getId() );
			}
			catch (Throwable t) {
				logger.error( "Current session destroyed is fail...\n{}" , ExceptionUtil.get( t ) );
			}
		}
	}
}